#!/usr/bin/python
# Copyright (C) 2014 Apple Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import filecmp
import fnmatch
import os
import re
import shutil
import sys
import datetime
import json

copyrightText = """ *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */\n
"""

generatorString = "/* Generated by %s do not hand edit. */\n" % os.path.basename(__file__)

functionHeadRegExp = re.compile(r"function\s+\w+\s*\(.*?\)", re.MULTILINE | re.S)
functionNameRegExp = re.compile(r"function\s+(\w+)\s*\(", re.MULTILINE | re.S)
functionParameterFinder = re.compile(r"^function\s+(?:\w+)\s*\(((?:\s*\w+)?\s*(?:\s*,\s*\w+)*)?\)", re.MULTILINE | re.S)

multilineCommentRegExp = re.compile(r"\/\*.*?\*\/", re.MULTILINE | re.S)
singleLineCommentRegExp = re.compile(r"\/\/.*?\n", re.MULTILINE | re.S)

def getCopyright(source):
    copyrightBlock = multilineCommentRegExp.findall(source)[0]
    copyrightBlock = copyrightBlock[:copyrightBlock.index("Redistribution")]
    copyRightLines = []

    for line in copyrightBlock.split("\n"):
        line = line.replace("/*", "")
        line = line.replace("*/", "")
        line = line.replace("*", "")
        line = line.replace("Copyright", "")
        line = line.replace("copyright", "")
        line = line.replace("(C)", "")
        line = line.replace("(c)", "")
        line = line.strip()
        if len(line) == 0:
            continue

        copyRightLines.append(line)
    
    return list(set(copyRightLines))



def getFunctions(source):

    source = multilineCommentRegExp.sub("/**/", singleLineCommentRegExp.sub("//\n", source))

    matches = [ f for f in functionHeadRegExp.finditer(source)]
    functionBounds = []
    start = 0
    end = 0
    for match in matches:
        start = match.start()
        if start < end:
            continue
        end = match.end()
        while source[end] != '{':
            end = end + 1
        depth = 1
        end = end + 1
        while depth > 0:
            if source[end] == '{':
                depth = depth + 1
            elif source[end] == '}':
                depth = depth - 1
            end = end + 1
        functionBounds.append((start, end))

    functions = [source[start:end].strip() for (start, end) in functionBounds]
    result = []
    for function in functions:
        function = multilineCommentRegExp.sub("", function)
        functionName = functionNameRegExp.findall(function)[0]
        functionParameters = functionParameterFinder.findall(function)[0].split(',')
        if len(functionParameters[0]) == 0:
            functionParameters = []
            
        result.append((functionName, function, functionParameters))
    return result
        

def generateCode(source):
    inputFile = open(source, "r")
    baseName = os.path.basename(source).replace(".js", "")
    
    source = ""
    for line in inputFile:
        source = source + line
    
    if sys.platform == "cygwin":
        source = source.replace("\r\n", "\n")
    return (baseName, getFunctions(source), getCopyright(source))

def mangleName(object, name):
    qName = object + "." + name
    mangledName = ""
    i = 0
    while i < len(qName):
        if qName[i] == '.':
            mangledName = mangledName + qName[i + 1].upper()
            i = i + 1
        else:
            mangledName = mangledName + qName[i]
        i = i + 1
    return mangledName

builtins = []

baseName = sys.argv[-1]
builtin_definitions = sys.argv[1:-1]
(output_base, _) = os.path.splitext(sys.argv[-1])

copyrights = []
for file in builtin_definitions:
    if fnmatch.fnmatch(file, '*.js'):
        (baseName, functions, objectCopyrights) = generateCode(file)
        copyrights.extend(objectCopyrights)
        builtins.append((baseName, functions))

copyrights = list(set(copyrights))

copyrightBody = ""
for copyright in copyrights:
    copyrightBody = copyrightBody +" * Copyright (C) " + copyright + "\n"

builtinsHeader = open(output_base + ".h.tmp", "w")
builtinsImplementation = open(output_base + ".cpp.tmp", "w")
copyrightText = "/*\n" + copyrightBody + copyrightText
builtinsHeader.write("""%s
%s

#ifndef JSCBuiltins_H
#define JSCBuiltins_H

namespace JSC {

class FunctionExecutable;
class Identifier;
class JSGlobalObject;
class SourceCode;
class UnlinkedFunctionExecutable;
class VM;

FunctionExecutable* createBuiltinExecutable(VM&, UnlinkedFunctionExecutable*, const SourceCode&);

""" % (generatorString, copyrightText))

codeReferences = []

for (objectName, functions) in builtins:
    print("Generating bindings for the %s builtin." % objectName)
    builtinsHeader.write("/* %s functions */\n" % objectName)
    for (name, implementation, _) in functions:
        mangledName = mangleName(objectName, name)
        mangledName = mangledName[0].lower() + mangledName[1:] + "Code"
        codeReferences.append((mangledName, name, implementation))
        builtinsHeader.write("extern const char* s_%s;\n" % mangledName)
        builtinsHeader.write("extern const int s_%sLength;\n" % mangledName)
    builtinsHeader.write("\n")
    builtinsHeader.write("#define JSC_FOREACH_%s_BUILTIN(macro) \\\n" % objectName.replace(".", "_").upper())
    for (name, implementation, arguments) in functions:
        mangledName = mangleName(objectName, name)
        builtinsHeader.write("    macro(%s, %s, %d) \\\n" % (name, mangledName, len(arguments)))
    builtinsHeader.write("\n")
    for (name, implementation, arguments) in functions:
        builtinsHeader.write("#define JSC_BUILTIN_%s 1\n" % mangleName(objectName, name).upper())
    builtinsHeader.write("\n\n")
names = []
builtinsHeader.write("#define JSC_FOREACH_BUILTIN(macro)\\\n")
for (codeReference, functionName, source) in codeReferences:
    builtinsHeader.write("    macro(%s, %s, s_%sLength) \\\n" % (codeReference, functionName, codeReference))
    names.append(functionName)

builtinsHeader.write("\n\n")
builtinsHeader.write("#define JSC_FOREACH_BUILTIN_FUNCTION_NAME(macro) \\\n")
for name in sorted(set(names)):
    builtinsHeader.write("    macro(%s) \\\n" % name)
builtinsHeader.write("""

#define JSC_DECLARE_BUILTIN_GENERATOR(codeName, functionName, argumentCount) \\
    FunctionExecutable* codeName##Generator(VM&);

JSC_FOREACH_BUILTIN(JSC_DECLARE_BUILTIN_GENERATOR)
#undef JSC_DECLARE_BUILTIN_GENERATOR

#define JSC_BUILTIN_EXISTS(name) defined JSC_BUILTIN_ ## name

}

#endif

""")

builtinsImplementation.write("""%s
%s

#include "config.h"

#include "JSCBuiltins.h"

#include "BuiltinExecutables.h"
#include "Executable.h"
#include "JSCellInlines.h"
#include "VM.h"

namespace JSC {

"""  % (generatorString, copyrightText))

for (codeReference, name, source) in codeReferences:
    source = "(function " + source[source.index("("):] + ")"
    lines = json.dumps(source)[1:-1].split("\\n")
    sourceLength = len(source)
    source = ""
    for line in lines:
        source = source + ("    \"%s\\n\" \\\n" % line)
    builtinsImplementation.write("const char* s_%s =\n%s;\n\n" % (codeReference, source))
    builtinsImplementation.write("const int s_%sLength = %d;\n\n" % (codeReference, sourceLength + 1)) # + 1 for \n

builtinsImplementation.write("""
FunctionExecutable* createBuiltinExecutable(VM& vm, UnlinkedFunctionExecutable* unlinkedExecutable, const SourceCode& source)
{
    unsigned lineCount = unlinkedExecutable->lineCount();
    unsigned startColumn = 1;
    unsigned sourceLength = unlinkedExecutable->sourceLength();
    bool endColumnIsOnStartLine = !lineCount;
    unsigned endColumnExcludingBraces = unlinkedExecutable->unlinkedBodyEndColumn() + (endColumnIsOnStartLine ? 0 : 1);
    unsigned startOffset = unlinkedExecutable->startOffset();
    unsigned startOffsetExcludingOpenBrace = startOffset + 1;
    unsigned endOffsetExcludingCloseBrace = startOffset + sourceLength - 1;
    SourceCode bodySource(source.provider(), startOffsetExcludingOpenBrace, endOffsetExcludingCloseBrace, 0, startColumn);
    return FunctionExecutable::create(vm, bodySource, unlinkedExecutable, 0, lineCount, startColumn, endColumnExcludingBraces, false);
}

#define JSC_DEFINE_BUILTIN_GENERATOR(codeName, functionName, argumentCount) \\
FunctionExecutable* codeName##Generator(VM& vm) \\
{ \\
    return createBuiltinExecutable(vm, vm.builtinExecutables()->codeName##Executable(), vm.builtinExecutables()->codeName##Source()); \\
}

JSC_FOREACH_BUILTIN(JSC_DEFINE_BUILTIN_GENERATOR)
#undef JSC_DEFINE_BUILTIN_GENERATOR
}

""")

builtinsHeader.close()
builtinsImplementation.close()

if (not os.path.exists(output_base + ".h")) or (not filecmp.cmp(output_base + ".h.tmp", output_base + ".h", shallow=False)):
    os.rename(output_base + ".h.tmp", output_base + ".h")
else:
    os.remove(output_base + ".h.tmp")

if (not os.path.exists(output_base + ".cpp")) or (not filecmp.cmp(output_base + ".cpp.tmp", output_base + ".cpp", shallow=False)):
    os.rename(output_base + ".cpp.tmp", output_base + ".cpp")
else:
    os.remove(output_base + ".cpp.tmp")
